//============================================================================
// Copyright (C) 2008-2013 Brett R. Jones 
//
// Issued to public domain 2013
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================
#include "VxNetLib.h"
#include <VxUtilLib/VxDefs.h>
#include <VxUtilLib/VxGlobals.h>
#include <VxUtilLib/VxSktUtil.h>
#include "VxSktBase.h"
#include <stdio.h>
#include <memory.h>

//============================================================================
// our receive function to receive Socket states etc
void VxSktBaseMgrRecieveFunction(  VxSktBase * poSkt )
{
	vx_assert( poSkt );
	vx_assert( poSkt->m_poSktMgr );
	poSkt->m_poSktMgr->DoRecieveCallback( poSkt );
}
//============================================================================
// our receive function to get Socket transmit stats
void VxSktBaseMgrTransmitFunction(  VxSktBase * poSkt )
{
	vx_assert( poSkt );
	vx_assert( poSkt->m_poSktMgr );
	poSkt->m_poSktMgr->DoTransmitCallback( poSkt );
}

//============================================================================
//=== constructor ===//
VxSktBaseMgr::VxSktBaseMgr()
	: m_rcLastError(0)
    , m_eSktMgrType(eSktMgrTypeNone)
	, m_pfnUserRecieve(0)
	, m_pfnOurRecieve(0)
	, m_pfnUserTransmit(0)
	, m_pfnOurTransmit(0)
	, m_u32MaxConnections( 10000 )
    , m_uiCreatorThreadId(0)
{
	m_pfnOurRecieve = VxSktBaseMgrRecieveFunction;
	m_uiCreatorThreadId = VxGetCurrentThreadId();
}

//============================================================================
//=== destructor ===//
VxSktBaseMgr::~VxSktBaseMgr()
{
}

//============================================================================
void VxSktBaseMgr::deleteSkt( VxSktBase * poSkt )
{ 
	if( m_aoSktsToDelete.size() >= MAX_DISCONNECTED_SKT_TO_KEEP_AROUND )
	{
		delete m_aoSktsToDelete[0];
		m_aoSktsToDelete.erase( m_aoSktsToDelete.begin() );
	}
	m_aoSktsToDelete.push_back( poSkt );
}	

//============================================================================
void VxSktBaseMgr::sktMgrSetLocalIp( VxInetAddress& oLclIp )
{
	m_oLclIp = oLclIp;
	m_oLclIp.toStdString( m_strLclIp );
}

//============================================================================
void VxSktBaseMgr::sktMgrShutdown( void )
{
	ShutdownAllSkts();
	DeleteAllSockets();
}

//============================================================================
SOCKET VxSktBaseMgr::MakeAndBindTcpSocket( VxInetAddress& /*oIpAddr*/, U16 u16Port )
{
	SOCKET oSkt = INVALID_SOCKET;
	struct addrinfo *servinfo, *p;
	struct addrinfo oHints;
	VxFillHints( oHints );

	int rv;
	char as8Port[16];
	sprintf( as8Port, "%d", u16Port );


	if ((rv = getaddrinfo(NULL, as8Port, &oHints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) 
	{
		if ((oSkt = socket(p->ai_family, p->ai_socktype,
			p->ai_protocol)) == -1) 
		{
				perror("server: socket");
				continue;
		}

		// allow same port to be reused
		int iReusePort = true;
		if( SOCKET_ERROR == setsockopt( oSkt, SOL_SOCKET,SO_REUSEADDR, (const char *)&iReusePort, sizeof(iReusePort)) ) 
		{
			// clear any old errors
            m_rcLastError = VxGetLastError();
            m_rcLastError = 0;
		}

		if( 0 > bind( oSkt, p->ai_addr, p->ai_addrlen ) ) 
		{
            VxCloseSkt( oSkt );
			oSkt = INVALID_SOCKET;
			perror("server: bind");
			continue;
		}

		break;
	}
	return oSkt;
}

//============================================================================
void VxSktBaseMgr::DeleteAllSockets()
{
	std::vector<VxSktBase *>::iterator oIter;
	for( oIter = m_aoSktsToDelete.begin(); oIter != m_aoSktsToDelete.end(); ++oIter )
	{
		delete (*oIter);
	}
	m_aoSktsToDelete.clear();
}

//============================================================================
//! User must Set Receive Callback
void VxSktBaseMgr::SetRecieveCallback( VX_SKT_CALLBACK pfnRecieve )
{
	//VxVerifyCodePtr( pfnRecieve );
	m_pfnUserRecieve = pfnRecieve;
}

//============================================================================
//! lock access from other threads
void VxSktBaseMgr::DoLock( void )
{
    m_oSktMgrMutex.Lock(__FILE__, __LINE__);
}

//============================================================================
//! unlock access from other threads
void VxSktBaseMgr::DoUnlock( void )
{
    m_oSktMgrMutex.Unlock(__FILE__, __LINE__);
}

//============================================================================
//! add a new socket to manage
void VxSktBaseMgr::AddSkt( VxSktBase * poSkt )
{
	DoLock();
	m_aoSkts.push_back( poSkt );
	DoUnlock();
}

//============================================================================
//! remove a socket from management
RCODE VxSktBaseMgr::RemoveSkt(  VxSktBase *	poSkt,		// skt to remove
								BOOL		bDelete )	// if true delete the skt
{
	std::vector<VxSktBase *>::iterator oIter;
	DoLock();
	for( oIter = m_aoSkts.begin(); oIter != m_aoSkts.end(); ++oIter )
	{
		if( (*oIter) == poSkt )
		{
			// found it in our list
			if( bDelete )
			{
				delete poSkt;
			}
			m_aoSkts.erase( oIter );
			DoUnlock();
			return 0;
		}
	}
	// socket not found
	DoUnlock();
	return -1;
}

//============================================================================
//! Send to all connections
RCODE VxSktBaseMgr::SendToAll(	char * pData,			// data to send
								int iDataLen )			// length of data
{
	RCODE rc = 0;
	RCODE rcTemp = 0;
	std::vector<VxSktBase *>::iterator oIter;
	DoLock();

	for( oIter = m_aoSkts.begin(); oIter != m_aoSkts.end(); ++oIter )
	{
		rcTemp = (*oIter)->SendData( pData, iDataLen );
		if( rcTemp &&
			( 0 == rc ) )
		{
			rc = rcTemp;
		}
	}

	DoUnlock();
	return rc;
}

//============================================================================
//! get number of connected sockets
int VxSktBaseMgr::GetConnectedCount( void )
{
	int iConnectedCnt = 0;
	std::vector<VxSktBase *>::iterator oIter;
	DoLock();
	for( oIter = m_aoSkts.begin(); oIter != m_aoSkts.end(); ++oIter )
	{
		if( (*oIter)->IsConnected() )
		{
			iConnectedCnt++;
		}
	}
	DoUnlock();
	return iConnectedCnt;
}

//============================================================================
//! close all sockets
void VxSktBaseMgr::CloseAllSkts( void )
{
	std::vector<VxSktBase *>::iterator oIter;
	// when socket is closed it gets removed from list so we have to be careful with list access
	int iConnectedCnt = GetConnectedCount();
	while( iConnectedCnt )
	{
		DoLock();
		for( oIter = m_aoSkts.begin(); oIter != m_aoSkts.end(); ++oIter )
		{
			if( (*oIter)->IsConnected() )
			{
				(*oIter)->CloseSkt();
				break;
			}
		}
		DoUnlock();
		iConnectedCnt = GetConnectedCount();
	}
}

//============================================================================
VxSktBase *	VxSktBaseMgr::makeNewSkt( void )					
{ 
	return new VxSktBase(); 
}


//============================================================================
//! close and remove all sockets
void VxSktBaseMgr::ShutdownAllSkts( void )
{
	CloseAllSkts();
}

//============================================================================
//! handle transmit callbacks from sockets
void VxSktBaseMgr::DoTransmitCallback( VxSktBase * poSkt )
{
	TxedPkt( poSkt->m_iLastTxLen );
}

//============================================================================
//! handle callbacks from sockets
void VxSktBaseMgr::DoRecieveCallback( VxSktBase * poSkt )
{
	// pass it along to user
	//VxVerifyCodePtr( m_pfnUserRecieve );
	ESktCallbackReason eCallbackReason = poSkt->GetCallbackReason();
	if( eSktCallbackReasonClosed == eCallbackReason )
	{
		log_msg( LOG_INFO, "VxSktBaseMgr::DoRecieveCallback: skt %d closed\n", poSkt->m_iSktId );
	}
	m_pfnUserRecieve( poSkt );
	if( eSktCallbackReasonData == eCallbackReason )
	{
		// do statistics
		RxedPkt( poSkt->m_iLastRxLen );
	}

	if( eSktCallbackReasonClosed == eCallbackReason )
	{
		// time to remove the socket
		DoLock();
		bool bErased = true;
		while( bErased )
		{
			bErased = false;
			std::vector<VxSktBase *>::iterator oIter = m_aoSktsToDelete.begin();
			while( oIter != m_aoSktsToDelete.end() )
			{
				VxSktBase * poToDeleteSkt = (*oIter);
				if( g_u32SysTimeSec - poToDeleteSkt->m_u32ToDeleteTime > 10 )
				{
					m_aoSktsToDelete.erase( oIter );
					log_msg( LOG_INFO, "deleting skt %d\n", poToDeleteSkt->m_iSktId );
					delete poToDeleteSkt;
					bErased = true;
					break;
				}
				else
				{
					++oIter;
				}
			}
		}

		// put skt in delete list to be deleted later
		std::vector<VxSktBase *>::iterator oSktIter;
		for( oSktIter = m_aoSkts.begin(); oSktIter != m_aoSkts.end(); ++oSktIter )
		{
			if( (*oSktIter) == poSkt )
			{
				poSkt->m_u32ToDeleteTime = g_u32SysTimeSec;
				m_aoSktsToDelete.push_back( poSkt );
				m_aoSkts.erase( oSktIter );
				break;
			}
		}
		DoUnlock();
	}
}
